/**
 * This file is part of jFlvTool.
 *
 * jFlvTool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * jFlvTool is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * file name  : BufferHelper.java
 * authors    : Jon Keys
 * created    : July 3, 2007, 5:51 PM
 * copyright  : Sony Digital Authoring Services
 *
 * modifications:
 * Date:        Name:           Description:
 * ----------   --------------- ----------------------------------------------
 * July 3, 2007    Jon Keys         Creation
 */

package com.sonydadc.dw.jflv.io;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;

import com.sonydadc.dw.jflv.metadata.AMFObject;
import com.sonydadc.dw.jflv.metadata.AMFTime;

/**
 *
 * @author Jon Keys
 */
public class BufferHelper {

    private byte[] mbb;
    private int pos;
    private boolean debug;
    private String str;
    private byte[] buf;

    /** Creates a new instance of BufferHelper */
    public BufferHelper() {
        mbb = null;
        pos = 0;
        debug = false;
        str = null;
        buf = null;
    }

    public void reset(){
        mbb = null;
        pos = 0;
        str = null;
        buf = null;
    }

    public ByteBuffer byte2buffer(byte[] bytes){

        ByteBuffer bbuf = ByteBuffer.allocate(bytes.length);
        bbuf.put(bytes);
        bbuf.rewind();

        return bbuf;

    }//byte2buffer()

    public int bit2uint(char[] bits){

        int uint = 0;

        for(int i=0;i<bits.length;i++){
            if(bits[i] == '1'){
                uint += Math.pow(2, (bits.length -i -1));
            }
        }

        return uint;

    }//bit2uint

    // read uint from existing byte[]
    public int readUint(byte[] mpb, int start, int len){

        int uint = 0;

        for(int i=0;i<len;i++){
            uint += (mpb[i+start] & 0xFF) << ((len -i -1)*8);
        }

        return uint;

    }//readUint()

    // read int from existing byte[]
    public int readInt(byte[] mpb, int start, int len){

        int uint = 0;

        for(int i=0;i<len;i++){
            uint += mpb[i+start];
        }

        return uint;

    }//readUint()

    // read binary string from existing byte[]
    public String readBinaryString(byte[] mpb, int start, int len){

        buf = new byte[len];
        System.arraycopy(mpb,start,buf,0,len);

        return new BigInteger(buf).toString(2);

    } //readBinaryString()

    // read String from existing byte[]
    public String readString(byte[] mpb, int start, int len){

        buf = new byte[len];
        str = null;

        try{

            System.arraycopy(mpb,start,buf,0,len);
            str = new String(buf);
            buf = null;

        }catch(Exception e){

            System.out.println("Error - could not read string from given bytes");
            if(debug){e.printStackTrace();}
            str = "";

        }

        return str;

    }//readString()

    // read uint from existing byte[]
    public double readDouble(byte[] mpb, int start, int len){

        ByteBuffer bbuf = ByteBuffer.allocate(len);
        buf = new byte[len];
        System.arraycopy(mpb,start,buf,0,len);
        bbuf.put(buf);
        bbuf.rewind();
        buf = null;

        return  bbuf.getDouble();

    }//readDouble()

    public void reverseByteArray(byte[] b){

        int left  = 0;
        int right = b.length-1;

        while (left < right) {

            byte temp = b[left];
            b[left]  = b[right];
            b[right] = temp;

            left++;
            right--;

        }//while

    }//reverse

    public Object getAMFData(){

        int amfSwtch = readUint(mbb, pos, 1);
        pos += 1;

        return getAMFData(amfSwtch);

    }//getAMFData()

    public Object getAMFData(int amfSwtch){

        Object amfData = null;

        switch(amfSwtch){

            case 0:
                amfData = getAMFDouble();
                break;

            case 1:
                amfData = getAMFBoolean();
                break;

            case 2:
                amfData = getAMFString();
                break;

            case 3:
                amfData = getAMFObject();
                break;

            case 8:
                amfData = getAMFMixedArray();
                break;

            case 10:
                amfData = getAMFArray();
                break;

            case 11:
                amfData = getAMFTime();
                break;

        }

        return amfData;

    }//getAMFData()

    public Double getAMFDouble(){

        double dbl = readDouble(mbb, pos, 8);
        pos += 8;

        return new Double(dbl);

    }//getAMFDouble()

    public Boolean getAMFBoolean(){

        int val = readUint(mbb, pos, 1);
        pos += 1;

        return new Boolean((val == 1));

    }//getAMFBoolean()

    public String getAMFString(){

        int bytes2read = readUint(mbb, pos, 2);
        pos += 2;

        String str = readString(mbb, pos, bytes2read);
        pos += bytes2read;

        return str;

    }//getAMFString()

    public AMFObject getAMFObject(){

        AMFObject amfObj = new AMFObject();

        String key = "";
        int type = 0;

        do{

            if(pos >= mbb.length){
                break;
            }

            key = getAMFString();
            type = readUint(mbb, pos, 1);
            pos += 1;

            amfObj.put(key,getAMFData(type));

        }while(!(key.length() < 1 && type == 9));

        return amfObj;

    }//getAMFObject()

    public HashMap<String,Object> getAMFMixedArray(){

        //just skip 4 bytes
        pos += 4;

        HashMap<String,Object> amfMap = new HashMap<String,Object>();

        String key = "";
        int type = 0;

        do{

            if(pos >= mbb.length){
                break;
            }

            key = getAMFString();
            type = readUint(mbb, pos, 1);
            pos += 1;

            amfMap.put(key,getAMFData(type));

        }while(!(key.length() < 1 && type == 9));

        return amfMap;

    }//getAMFMixedArray()

    public ArrayList<Object> getAMFArray(){

        int size = readUint(mbb, pos, 4);
        pos += 4;

        ArrayList<Object> afmArray = new ArrayList<Object>();

        for(int i=0;i<size;i++){
            afmArray.add(getAMFData());
        }

        return afmArray;

    }//getAMFArray()

    public AMFTime getAMFTime(){

        //get time in milliseconds
        long time = (long)getAMFDouble().doubleValue();

        byte[] buf = new byte[2];
        System.arraycopy(mbb,pos,buf,0,2);
        pos += 2;

        reverseByteArray(buf);

        //get gmt offset in milliseconds
        int gmtOff = 0;
        for(int i=0;i<2;i++){
            gmtOff += (buf[i] & 0xFF) << ((1 -i)*8);
        }
        buf = null;
        int gmt = gmtOff * 60 * 1000;

        return new AMFTime(time, gmt);

    }//getAMFTime()


    public void clearData(){
        this.mbb = null;
    }

    public byte[] getBuffer() {
        return mbb;
    }

    public void setBuffer(byte[] mbb) {
        this.mbb = mbb;
    }

    public boolean isDebug() {
        return debug;
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

}//BufferHelper
